"""
XML Configuration Parser for GCR Solver Manager
Provides Python interface to XML-based configuration system
"""

import xml.etree.ElementTree as ET
from pathlib import Path
from typing import Dict, Any, Optional, Union, List
import logging

logger = logging.getLogger(__name__)


class Parameter:
    """Represents a single configuration parameter."""
    
    def __init__(self, name: str, param_type: str, value: str):
        self.name = name
        self.type = param_type
        self.value = value
    
    def as_int(self) -> int:
        """Convert parameter value to integer."""
        return int(self.value)
    
    def as_double(self) -> float:
        """Convert parameter value to float."""
        return float(self.value)
    
    def as_bool(self) -> bool:
        """Convert parameter value to boolean."""
        return self.value.lower() in ('true', '1', 'yes', 'on')
    
    def as_string(self) -> str:
        """Return parameter value as string."""
        return self.value


class ParameterList:
    """Represents a list of parameters and sublists."""
    
    def __init__(self, name: str = ""):
        self.name = name
        self.parameters: Dict[str, Parameter] = {}
        self.sublists: Dict[str, 'ParameterList'] = {}
    
    def has_parameter(self, name: str) -> bool:
        """Check if parameter exists."""
        return name in self.parameters
    
    def get_parameter(self, name: str) -> Parameter:
        """Get parameter by name."""
        if name not in self.parameters:
            raise KeyError(f"Parameter '{name}' not found in list '{self.name}'")
        return self.parameters[name]
    
    def set_parameter(self, name: str, param_type: str, value: str):
        """Set a parameter."""
        self.parameters[name] = Parameter(name, param_type, value)
    
    def get_int(self, name: str, default_val: int = 0) -> int:
        """Get integer parameter with default."""
        if name in self.parameters:
            return self.parameters[name].as_int()
        return default_val
    
    def get_double(self, name: str, default_val: float = 0.0) -> float:
        """Get float parameter with default."""
        if name in self.parameters:
            return self.parameters[name].as_double()
        return default_val
    
    def get_bool(self, name: str, default_val: bool = False) -> bool:
        """Get boolean parameter with default."""
        if name in self.parameters:
            return self.parameters[name].as_bool()
        return default_val
    
    def get_string(self, name: str, default_val: str = "") -> str:
        """Get string parameter with default."""
        if name in self.parameters:
            return self.parameters[name].as_string()
        return default_val
    
    def has_sublist(self, name: str) -> bool:
        """Check if sublist exists."""
        return name in self.sublists
    
    def get_sublist(self, name: str) -> 'ParameterList':
        """Get sublist by name."""
        if name not in self.sublists:
            # Create empty sublist if it doesn't exist
            self.sublists[name] = ParameterList(name)
        return self.sublists[name]
    
    def add_sublist(self, name: str, sublist: 'ParameterList'):
        """Add a sublist."""
        self.sublists[name] = sublist
    
    def get_parameter_names(self) -> List[str]:
        """Get all parameter names."""
        return list(self.parameters.keys())
    
    def get_sublist_names(self) -> List[str]:
        """Get all sublist names."""
        return list(self.sublists.keys())
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary for easy access."""
        result = {}
        
        # Add parameters
        for name, param in self.parameters.items():
            if param.type == "int":
                result[name] = param.as_int()
            elif param.type == "double":
                result[name] = param.as_double()
            elif param.type == "bool":
                result[name] = param.as_bool()
            else:
                result[name] = param.as_string()
        
        # Add sublists
        for name, sublist in self.sublists.items():
            result[name] = sublist.to_dict()
        
        return result
    
    def print(self, indent: int = 0):
        """Print parameter list with indentation."""
        prefix = "  " * indent
        print(f"{prefix}ParameterList: {self.name}")
        
        for name, param in self.parameters.items():
            print(f"{prefix}  {name} ({param.type}): {param.value}")
        
        for name, sublist in self.sublists.items():
            sublist.print(indent + 1)


class XMLConfigParser:
    """XML configuration file parser."""
    
    def __init__(self):
        self.xml_content = ""
        self.last_error = ""
    
    def load_from_file(self, filename: Union[str, Path]) -> bool:
        """Load XML configuration from file."""
        try:
            with open(filename, 'r') as f:
                self.xml_content = f.read()
            return True
        except Exception as e:
            self.last_error = f"Failed to load file {filename}: {e}"
            logger.error(self.last_error)
            return False
    
    def load_from_string(self, xml_string: str) -> bool:
        """Load XML configuration from string."""
        try:
            self.xml_content = xml_string
            return True
        except Exception as e:
            self.last_error = f"Failed to load XML string: {e}"
            logger.error(self.last_error)
            return False
    
    def parse(self) -> ParameterList:
        """Parse XML content into ParameterList."""
        try:
            root = ET.fromstring(self.xml_content)
            
            if root.tag != "ParameterList":
                raise ValueError("Root element must be ParameterList")
            
            root_name = root.get("name", "Root")
            param_list = ParameterList(root_name)
            
            self._parse_parameter_list(root, param_list)
            
            return param_list
            
        except Exception as e:
            self.last_error = f"Failed to parse XML: {e}"
            logger.error(self.last_error)
            return ParameterList("Error")
    
    def _parse_parameter_list(self, xml_element: ET.Element, param_list: ParameterList):
        """Recursively parse ParameterList elements."""
        for child in xml_element:
            if child.tag == "Parameter":
                name = child.get("name")
                param_type = child.get("type", "string")
                value = child.get("value", "")
                
                if name:
                    param_list.set_parameter(name, param_type, value)
                    
            elif child.tag == "ParameterList":
                sublist_name = child.get("name", "Unnamed")
                sublist = ParameterList(sublist_name)
                self._parse_parameter_list(child, sublist)
                param_list.add_sublist(sublist_name, sublist)
    
    def validate(self) -> bool:
        """Validate XML content."""
        try:
            ET.fromstring(self.xml_content)
            return True
        except Exception as e:
            self.last_error = f"XML validation failed: {e}"
            return False
    
    def get_last_error(self) -> str:
        """Get last error message."""
        return self.last_error


class SolverConfig:
    """Global solver configuration manager."""
    
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance.initialized = False
        return cls._instance
    
    def __init__(self):
        if not hasattr(self, 'initialized') or not self.initialized:
            self.config = ParameterList("SolverConfig")
            self.config_file = ""
            self.initialized = False
    
    @classmethod
    def get_instance(cls) -> 'SolverConfig':
        """Get singleton instance."""
        return cls()
    
    def initialize(self, config_file: Union[str, Path]) -> bool:
        """Initialize configuration from file."""
        parser = XMLConfigParser()
        
        if not parser.load_from_file(config_file):
            logger.error(f"Failed to load config file: {config_file}")
            return False
        
        self.config = parser.parse()
        self.config_file = str(config_file)
        self.initialized = True
        
        logger.info(f"Configuration loaded from: {config_file}")
        return True
    
    def initialize_from_dict(self, config_dict: Dict[str, Any]) -> bool:
        """Initialize configuration from dictionary."""
        try:
            self.config = self._dict_to_parameter_list(config_dict, "Config")
            self.initialized = True
            return True
        except Exception as e:
            logger.error(f"Failed to initialize from dict: {e}")
            return False
    
    def _dict_to_parameter_list(self, data: Dict[str, Any], name: str) -> ParameterList:
        """Convert dictionary to ParameterList."""
        param_list = ParameterList(name)
        
        for key, value in data.items():
            if isinstance(value, dict):
                sublist = self._dict_to_parameter_list(value, key)
                param_list.add_sublist(key, sublist)
            else:
                # Determine type
                if isinstance(value, bool):
                    param_type = "bool"
                elif isinstance(value, int):
                    param_type = "int"
                elif isinstance(value, float):
                    param_type = "double"
                else:
                    param_type = "string"
                
                param_list.set_parameter(key, param_type, str(value))
        
        return param_list
    
    def get_config(self) -> ParameterList:
        """Get configuration parameter list."""
        if not self.initialized:
            logger.warning("Configuration not initialized, returning empty config")
        return self.config
    
    # Convenience accessors
    def get_gpu_backend(self) -> str:
        """Get GPU backend type."""
        return self.config.get_sublist("System").get_string("gpu_backend", "none")
    
    def get_communication(self) -> str:
        """Get communication type."""
        return self.config.get_sublist("System").get_string("communication", "mpi")
    
    def get_solver_type(self) -> str:
        """Get solver type."""
        return self.config.get_sublist("Solver").get_string("type", "gcr")
    
    def get_total_processes(self) -> int:
        """Get total MPI processes."""
        return self.config.get_sublist("MPI").get_int("total_processes", 4)
    
    def get_nprocx(self) -> int:
        """Get processes in X direction."""
        return self.config.get_sublist("MPI").get_int("nprocx", 2)
    
    def get_max_iterations(self) -> int:
        """Get maximum iterations."""
        return self.config.get_sublist("Solver").get_int("max_iterations", 20)
    
    def get_resolution(self) -> float:
        """Get problem resolution."""
        return self.config.get_sublist("Problem").get_double("resolution", 1.0)
    
    def is_debug_mode(self) -> bool:
        """Check if debug mode is enabled."""
        return self.config.get_sublist("System").get_bool("use_debug", False)
    
    def use_random_data(self) -> bool:
        """Check if using random data."""
        return self.config.get_sublist("System").get_bool("use_random_data", True)
    
    def override_parameter(self, section: str, name: str, param_type: str, value: str):
        """Override a parameter value (for command line args)."""
        sublist = self.config.get_sublist(section)
        sublist.set_parameter(name, param_type, value)
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert configuration to dictionary."""
        return self.config.to_dict()
    
    def export_config(self, filename: Union[str, Path]):
        """Export current configuration to XML file."""
        xml_content = self._parameter_list_to_xml(self.config)
        
        with open(filename, 'w') as f:
            f.write(xml_content)
    
    def _parameter_list_to_xml(self, param_list: ParameterList, indent: int = 0) -> str:
        """Convert ParameterList to XML string."""
        prefix = "  " * indent
        xml_lines = [f'{prefix}<ParameterList name="{param_list.name}">']
        
        # Add parameters
        for name, param in param_list.parameters.items():
            xml_lines.append(f'{prefix}  <Parameter name="{name}" type="{param.type}" value="{param.value}"/>')
        
        # Add sublists
        for name, sublist in param_list.sublists.items():
            xml_lines.append(self._parameter_list_to_xml(sublist, indent + 1))
        
        xml_lines.append(f'{prefix}</ParameterList>')
        return '\n'.join(xml_lines)
    
    def print_config(self):
        """Print current configuration."""
        if self.initialized:
            print(f"Configuration from: {self.config_file}")
            self.config.print()
        else:
            print("Configuration not initialized")
    
    def validate_config(self) -> bool:
        """Validate current configuration."""
        errors = self.get_validation_errors()
        return len(errors) == 0
    
    def get_validation_errors(self) -> List[str]:
        """Get list of configuration validation errors."""
        errors = []
        
        # Validate required sections exist
        required_sections = ["System", "MPI", "Problem", "Solver"]
        for section in required_sections:
            if not self.config.has_sublist(section):
                errors.append(f"Missing required section: {section}")
        
        # Validate GPU backend
        gpu_backend = self.get_gpu_backend()
        if gpu_backend not in ["cuda", "hip", "none"]:
            errors.append(f"Invalid GPU backend: {gpu_backend}")
        
        # Validate solver type
        solver_type = self.get_solver_type()
        if solver_type not in ["gcr", "ca-gcr", "gmres", "bca-gmres"]:
            errors.append(f"Invalid solver type: {solver_type}")
        
        # Validate MPI configuration
        total_procs = self.get_total_processes()
        nprocx = self.get_nprocx()
        if total_procs <= 0:
            errors.append("Total processes must be positive")
        if nprocx <= 0:
            errors.append("nprocx must be positive")
        if total_procs % nprocx != 0:
            errors.append("Total processes must be divisible by nprocx")
        
        return errors


# Global instance
def get_solver_config() -> SolverConfig:
    """Get global solver configuration instance."""
    return SolverConfig.get_instance()